package event

import (
	"encoding/xml"
	"strconv"
	"strings"
)

type Event interface {
	Handle() interface{}
	OnParse(params map[string]string) Event
}

type WeChatRequestMessage struct {
	ToUserName   string
	FromUserName string
	CreateTime   int
	MsgType      string
}

type XmlData struct {
	Value string `xml:",cdata"`
}

type WeChatResponseMessage struct {
	XMLName      xml.Name `xml:"xml"`
	ToUserName   XmlData  `xml:"ToUserName"`
	FromUserName XmlData  `xml:"FromUserName"`
	CreateTime   int      `xml:"CreateTime"`
	MsgType      XmlData  `xml:"MsgType"`
}

type TextResponseMessage struct {
	*WeChatResponseMessage
	Content string `xml:"Content"`
}

func ParseEvent(message string) Event {
	var evt Event
	params := xml2Params(message)
	msg := &WeChatRequestMessage{
		ToUserName:   params["tousername"],
		FromUserName: params["fromusername"],
		MsgType:      params["msgtype"],
	}
	msg.CreateTime, _ = strconv.Atoi(params["createtime"])
	switch strings.ToLower(msg.MsgType) {
	case "event":
		evt = &PushEvent{WeChatRequestMessage: msg}
	case "text":
		evt = &TextEvent{WeChatRequestMessage: msg}
	default:
		evt = &EventNotFound{}
	}

	return evt.OnParse(params)
}

func CreateTextResponseMessage(rq *WeChatRequestMessage, text string) *TextResponseMessage {
	return &TextResponseMessage{
		WeChatResponseMessage: &WeChatResponseMessage{
			FromUserName: XmlData{rq.ToUserName},
			ToUserName:   XmlData{rq.FromUserName},
			CreateTime:   rq.CreateTime + 1,
			MsgType:      XmlData{"text"},
		},
		Content: text,
	}
}

func xml2Params(input string) map[string]string {
	var (
		err  error
		t    xml.Token
		name string
	)

	params := make(map[string]string)
	decoder := xml.NewDecoder(strings.NewReader(input))
	for t, err = decoder.Token(); err == nil; t, err = decoder.Token() {
		switch token := t.(type) {
		case xml.StartElement:
			name = strings.ToLower(token.Name.Local)
		case xml.CharData:
			data := strings.TrimSpace(string([]byte(token)))
			if name != "" && data != "" {
				params[name] = data
			}
		default:
		}
	}
	return params
}
